import streamlit as st
import pm4py
import pandas as pd
from pm4py.objects.conversion.log import converter as log_converter
from pm4py.visualization.petri_net import visualizer as pn_visualizer
import tempfile
import os
import io

def load_event_log(uploaded_file):
    """加载事件日志文件(CSV或XES格式)"""
    try:
        if uploaded_file.name.endswith('.csv'):
            # 读取CSV文件
            df = pd.read_csv(uploaded_file)
            # 确保必要的列存在
            required_columns = ['case:concept:name', 'concept:name', 'time:timestamp']
            if not all(col in df.columns for col in required_columns):
                st.warning('CSV文件必须包含以下列：case:concept:name（案例ID）, concept:name（活动名称）, time:timestamp（时间戳）')
                return None
            # 转换为事件日志格式
            log = log_converter.apply(df)
        elif uploaded_file.name.endswith('.xes'):
            # 将上传的文件内容保存到临时文件
            with tempfile.NamedTemporaryFile(delete=False, suffix='.xes') as tmp_file:
                tmp_file.write(uploaded_file.getvalue())
                tmp_file_path = tmp_file.name
            # 读取XES文件
            log = pm4py.read_xes(tmp_file_path)
            # 删除临时文件
            os.unlink(tmp_file_path)
        return log
    except Exception as e:
        st.error(f'读取文件时出错：{str(e)}')
        return None

def discover_process_model(log, algorithm):
    """使用选定的算法发现过程模型"""
    try:
        if algorithm == "Alpha挖掘算法":
            net, initial_marking, final_marking = pm4py.discover_petri_net_alpha(log)
        elif algorithm == "Alpha+挖掘算法":
            net, initial_marking, final_marking = pm4py.discover_petri_net_alpha_plus(log)
        elif algorithm == "启发式挖掘算法":
            net, initial_marking, final_marking = pm4py.discover_petri_net_heuristics(log)
        return net, initial_marking, final_marking
    except Exception as e:
        st.error(f'进行过程挖掘时出错：{str(e)}')
        st.error(f'错误类型: {type(e).__name__}')
        st.error(f'错误详情: {str(e)}')
        return None, None, None

def main():
    st.set_page_config(page_title="过程挖掘分析工具", layout="wide")
    
    st.title("过程挖掘分析工具")
    
    # 更新使用说明
    with st.expander("使用说明"):
        st.markdown("""
        ### 使用步骤：
        1. 上传事件日志文件（支持.csv或.xes格式）
        2. 选择想要使用的挖掘算法
        3. 点击"开始挖掘"按钮
        
        ### CSV文件要求：
        - 必须包含以下列：
          - case:concept:name（案例ID）
          - concept:name（活动名称）
          - time:timestamp（时间戳）
        
        ### 支持的算法：
        - Alpha挖掘算法：基础的过程挖掘算法，适用于结构化程度较高的过程
        - Alpha+挖掘算法：Alpha算法的改进版本，可以处理短循环
        - 启发式挖掘算法：基于频率的挖掘算法，能够处理噪声数据，更适合实际业务场景
        
        ### 算法特点：
        1. **Alpha挖掘算法**
           - 优点：简单直观，容易理解
           - 缺点：无法处理噪声、循环等复杂情况
        
        2. **Alpha+挖掘算法**
           - 优点：可以发现长度为1或2的循环
           - 缺点：计算复杂度较高
        
        3. **启发式挖掘算法**
           - 优点：抗噪声能力强，适合实际业务场景
           - 缺点：参数设置可能影响挖掘结果
        """)
    
    # 文件上传
    uploaded_file = st.file_uploader("请上传事件日志文件", type=['csv', 'xes'])
    
    if uploaded_file is not None:
        st.info(f'已选择文件：{uploaded_file.name}')
        
        # 更新算法选择列表
        algorithm = st.selectbox(
            "选择挖掘算法",
            ["Alpha挖掘算法", "Alpha+挖掘算法", "启发式挖掘算法"],
            help="选择用于发现过程模型的算法"
        )
        
        progress_bar = st.progress(0)
        
        if st.button("开始挖掘", help="点击开始进行过程挖掘"):
            with st.spinner('正在加载事件日志...'):
                progress_bar.progress(30)
                log = load_event_log(uploaded_file)
                
                if log is not None:
                    progress_bar.progress(60)
                    with st.spinner('正在进行过程挖掘...'):
                        net, initial_marking, final_marking = discover_process_model(log, algorithm)
                        
                        if net is not None:
                            progress_bar.progress(90)
                            with st.spinner('正在生成可视化结果...'):
                                try:
                                    # 可视化Petri网
                                    gviz = pn_visualizer.apply(net, initial_marking, final_marking)
                                    
                                    # 创建临时文件但确保正确关闭和删除
                                    temp_dir = tempfile.mkdtemp()
                                    temp_file_path = os.path.join(temp_dir, "petri_net.png")
                                    
                                    try:
                                        # 保存图像
                                        pn_visualizer.save(gviz, temp_file_path)
                                        
                                        # 读取图像文件
                                        with open(temp_file_path, "rb") as f:
                                            image_bytes = f.read()
                                        
                                        # 显示图像
                                        st.image(image_bytes, caption=f"使用{algorithm}生成的Petri网")
                                        
                                    finally:
                                        # 清理临时文件和目录
                                        if os.path.exists(temp_file_path):
                                            os.remove(temp_file_path)
                                        if os.path.exists(temp_dir):
                                            os.rmdir(temp_dir)
                                    
                                    progress_bar.progress(100)
                                    st.success(f"使用{algorithm}成功生成Petri网!")
                                except Exception as e:
                                    st.error(f"生成可视化结果时出错：{str(e)}")

if __name__ == "__main__":
    main() 